﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using WindowsPhoneEssentials.Abstractions;
using WindowsPhoneEssentials.Diagnostics;
using WindowsPhoneEssentials.Storage;
using WindowsPhoneEssentials.Tasks;
using WindowsPhoneEssentials.Threading;
using WindowsPhoneMVC.Framework;
using WindowsPhoneMVC.Framework.Interfaces;
using WindowsPhoneMVC.Interfaces;
using WindowsPhoneMVC.Navigation;
using WindowsPhoneMVC.Navigation.Interfaces;

namespace WindowsPhoneMVC
{
    public class PhoenixApplicationService : DependencyObject, IApplicationService, IPhoenixApplication
    {
        private static readonly TraceScope initialisationScope;
        private static readonly TraceScope ctorScope;
        internal static TraceScope StartupScope;
        protected static Assembly ApplicationAssembly = Application.Current.GetType().Assembly;
        private static readonly Type navigationApplicationType = typeof(PhoenixApplicationService);

        static PhoenixApplicationService()
        {
#if DEBUG
            Trace.Appenders.Add(s => Debug.WriteLine(s));
            Trace.TraceLevel = TraceLevel.Info;
            Trace.ClassNameLength = 30;
#endif
            StartupScope = TraceScope.Create(navigationApplicationType, TraceLevel.Info, () => "Starting Application");
            initialisationScope = TraceScope.Create(navigationApplicationType, TraceLevel.Debug, () => "Navigation Application Initialising");
            ctorScope = TraceScope.Create(navigationApplicationType, TraceLevel.Debug, () => "Construcing MVC Infrastructure");
        }

        private readonly TraceScope waitingForPhoneToStartServiceScope;
        private readonly IPhoneApplicationFrame applicationFrame;
        private readonly IControllerLocator controllerLocator;
        private readonly IViewLocator viewLocator;
        private readonly ITaskManager taskManager;
        private readonly IBackgroundWorker backgroundWorker;
        private IActionResolver actionResolver;
        private IPhoneApplicationService phoneApplicationService;
        private NavigationContextFactory navigationContextFactory;
        private PhoenixBootstrapper bootstrapper;
        private Navigator navigator;

        public PhoenixApplicationService() : this(
            null,
            new TransientStore(),
            new DefaultControllerLocator(ApplicationAssembly),
            new DefaultControllerFactory(),
            new DefaultViewLocator(ApplicationAssembly),
            new PhoneTaskManager(),
            new BackgroundWorker(),
            new DefaultActionResolver())
        { }

        public PhoenixApplicationService(
            IPhoneApplicationFrame applicationFrame,
            ITransientStore applicationTransientStore, 
            IControllerLocator controllerLocator, 
            IControllerFactory controllerFactory, 
            IViewLocator viewLocator, 
            ITaskManager taskManager, 
            IBackgroundWorker backgroundWorker,
            IActionResolver actionResolver,
            IPhoneApplicationService phoneApplicationService = null)
        {
            if (applicationFrame == null)
            {
                using (TraceScope.Create(navigationApplicationType, TraceLevel.Debug, () => "Creating PhoneApplicationFrame"))
                    applicationFrame = new PhoneApplicationFrameWrapper(new PhoneApplicationFrame());
            }
            ApplicationTransientState = applicationTransientStore;
            ControllerFactory = controllerFactory;
            this.applicationFrame = applicationFrame;
            this.controllerLocator = controllerLocator;
            this.viewLocator = viewLocator;
            this.taskManager = taskManager;
            this.backgroundWorker = backgroundWorker;
            this.actionResolver = actionResolver;

            if (phoneApplicationService != null)
            {
                this.phoneApplicationService = phoneApplicationService;
            }
            else if (PhoneApplicationService.Current == null)
            {
                this.phoneApplicationService = new PhoneApplicationServiceWrapper(new PhoneApplicationService());
            }

            applicationFrame.Navigated += ApplicationNavigated;

#if WINDOWS_PHONE71
            if (applicationFrame.UriMapper == null)
                applicationFrame.UriMapper = new UriMapper();
            if (applicationFrame.UriMapper is UriMapper)
            {
                ((UriMapper)applicationFrame.UriMapper).UriMappings.Add(new UriMapping
                                                                        {
                                                                            Uri = new Uri("/SearchExtras", UriKind.Relative),
                                                                            MappedUri = new Uri("/Shell.xaml?SearchExtras", UriKind.Relative)
                                                                        });

            }
#endif
            ctorScope.Dispose();
            waitingForPhoneToStartServiceScope = TraceScope.Create(navigationApplicationType, TraceLevel.Debug, () => "Wait for Phone to Start MVC Service...");
        }

        public void StartService(ApplicationServiceContext context)
        {
            waitingForPhoneToStartServiceScope.Dispose();
            using (TraceScope.Create(navigationApplicationType, TraceLevel.Debug, () => "Starting PhoenixApplicationService Service"))
            {
                if (phoneApplicationService == null)
                    phoneApplicationService = new PhoneApplicationServiceWrapper(PhoneApplicationService.Current);
                if (!Application.Current.ApplicationLifetimeObjects.OfType<PhoneApplicationService>().Any())
                    PhoneApplicationService.Current.StartService(context);

                phoneApplicationService.Activated += ApplicationActivated;
                phoneApplicationService.Deactivated += ApplicationDeactivated;

                navigationContextFactory = new NavigationContextFactory(viewLocator, taskManager, this);
                navigator = new Navigator(controllerLocator, navigationContextFactory, backgroundWorker, ControllerFactory, actionResolver);

                bootstrapper = new PhoenixBootstrapper(navigator, this,
                                                               applicationFrame, phoneApplicationService,
                                                               DefaultNavigation,
                                                               new Journal(FrameName),
#if WINDOWS_PHONE71
                                                               AppConnectAction);
#else
                                                               null);
#endif

                using (TraceScope.Create(navigationApplicationType, TraceLevel.Info, () => "Raising PhoenixApplicationService.Initialised event handlers (user code)"))
                    OnInitialised(new NavigationApplicationInitialisedEventArgs(this));
            }

            initialisationScope.Dispose();
        }

        private void DefaultNavigation(INavigator n)
        {
            n.Invoke(DefaultController, DefaultAction);
        }

        public void StopService()
        {
        }

        public void AddAssemblyToScan(Assembly assembly)
        {
            controllerLocator.AddAssemblyToScan(assembly);
            viewLocator.AddAssemblyToScan(assembly);
        }

        private void ApplicationActivated(object sender, ActivatedEventArgs e)
        {
            bootstrapper.InitializingAfterTombstoning = true;
        }

        private void ApplicationDeactivated(object sender, DeactivatedEventArgs e)
        {
            OnDeactivated(sender, e);
            navigator.SaveState();
        }

        private void ApplicationNavigated(object sender, NavigationEventArgs e)
        {
            bootstrapper.NavigationAppStartup(e);
        }

        public ITransientStore ApplicationTransientState { get; private set; }

        protected IControllerFactory ControllerFactory { get; private set; }

        #region public string FrameName
        public static readonly DependencyProperty FrameNameProperty =
            DependencyProperty.Register("FrameName", typeof(string), navigationApplicationType, new PropertyMetadata("DefaultFrame"));

        public string FrameName
        {
            get { return (string)GetValue(FrameNameProperty); }
            set { SetValue(FrameNameProperty, value); }
        }
        #endregion

        #region public string DefaultController
        public static readonly DependencyProperty DefaultControllerProperty =
            DependencyProperty.Register("DefaultController", typeof(string), navigationApplicationType, new PropertyMetadata("Home"));

        public string DefaultController
        {
            get { return (string)GetValue(DefaultControllerProperty); }
            set { SetValue(DefaultControllerProperty, value); }
        }

        #endregion

        #region public string DefaultAction
        public static readonly DependencyProperty DefaultActionProperty =
            DependencyProperty.Register("DefaultAction", typeof(string), navigationApplicationType, new PropertyMetadata("MainPage"));

        public string DefaultAction
        {
            get { return (string)GetValue(DefaultActionProperty); }
            set { SetValue(DefaultActionProperty, value); }
        }
        #endregion

#if WINDOWS_PHONE71
        #region public string AppConnectAction
        public static readonly DependencyProperty AppConnectActionProperty =
            DependencyProperty.Register("AppConnectAction", typeof(string), navigationApplicationType, new PropertyMetadata(default(string)));


        public string AppConnectAction
        {
            get { return (string)GetValue(AppConnectActionProperty); }
            set { SetValue(AppConnectActionProperty, value); }
        }
        #endregion
#endif

        #region public event EventHandler<DeactivatedEventArgs> Deactivated
        public event EventHandler<DeactivatedEventArgs> Deactivated;

        private void OnDeactivated(object sender, DeactivatedEventArgs e)
        {
            var handler = Deactivated;
            if (handler != null) handler(sender, e);
        }
        #endregion

        #region public event EventHandler<NavigationApplicationInitialisedEventArgs> Initialised
        public event EventHandler<NavigationApplicationInitialisedEventArgs> Initialised;

        private void OnInitialised(NavigationApplicationInitialisedEventArgs e)
        {
            var handler = Initialised;
            if (handler != null) handler(this, e);
        }
        #endregion

        #region public event EventHandler<ActivatedEventArgs> Activated
        public event EventHandler<ActivatedEventArgs> Activated
        {
            add { phoneApplicationService.Activated += value; }
            remove { phoneApplicationService.Activated -= value; }
        }
        #endregion

        #region public event EventHandler<ClosingEventArgs> Closing
        public event EventHandler<ClosingEventArgs> Closing
        {
            add { phoneApplicationService.Closing += value; }
            remove { phoneApplicationService.Closing -= value; }
        }
        #endregion

        #region public event EventHandler<ObscuredEventArgs> Obscured
        public event EventHandler<ObscuredEventArgs> Obscured
        {
            add { applicationFrame.Obscured += value; }
            remove { applicationFrame.Obscured -= value; }
        }
        #endregion

        #region public event EventHandler Unobscured
        public event EventHandler Unobscured
        {
            add { applicationFrame.Unobscured += value; }
            remove { applicationFrame.Unobscured -= value; }
        }
        #endregion
    }
}
